/*
 * Created on 26.11.2003
 */
package de.javakicker;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.awt.Dimension;
import java.awt.Point;

/**
 * The Class GameEngine.
 *
 * @author Till Krullmann
 */
public class GameEngine implements GameInfo
{

	/**
	 * The Class Actor.
	 */
	private abstract class Actor
	{
		
		/** The position. */
		private Point position;

		/**
		 * Gets the position.
		 *
		 * @return the position
		 */
		public Point getPosition()
		{ return position; }

		/**
		 * Sets the position.
		 *
		 * @param pos the new position
		 */
		public void setPosition(Point pos)
		{ position = (pos == null) ? null : new Point(pos); }
	}


	/**
	 * The Class RuntimePlayerImpl.
	 */
	public class RuntimePlayerImpl extends Actor implements RuntimePlayer
	{
		
		/** The player. */
		private Player player;
		
		/** The player name. */
		private String playerName;
		
		/** The player no. */
		private String playerNo;
		
		/** The java kicker id. */
		private String javaKickerId;
		
		/** The goals. */
		private Integer goals = 0;
		
		/** The touches. */
		private Integer touches = 0;
		
		/** The team. */
		private Team team;
		
		/** The fouls. */
		private int fouls = 0;
		
		/** The comment. */
		private String comment = null;

		/** Eine Liste mit den Nachrichten f�r diesen Spieler. */
		private LinkedList <Object> messages = new LinkedList<Object>();

		/** Der Roboter, der als letzter eine Nachricht an diesen Roboter
		 * gesendet hat. Wird gemerkt, damit nicht mehrere Nachrichten
		 * des gleichen Absenders gespeichert werden. */
		private Object lastSender = null;

		/**
		 * Instantiates a new runtime player impl.
		 *
		 * @param team the team
		 * @param lineupPlayer the lineup player
		 * @param javaKickerId the java kicker id
		 * @throws ClassNotFoundException the class not found exception
		 * @throws InvocationTargetException the invocation target exception
		 * @throws InvalidPlayerException the invalid player exception
		 */
		public RuntimePlayerImpl(Team team, Lineup.Player lineupPlayer, int javaKickerId)
			throws ClassNotFoundException, InvocationTargetException,
			InvalidPlayerException
		{
			this(team, lineupPlayer.newInstance(), lineupPlayer.getName(), lineupPlayer.getNo());
			this.comment = lineupPlayer.getComment();
			this.javaKickerId = String.valueOf(javaKickerId);
		}


		/**
		 * Instantiates a new runtime player impl.
		 *
		 * @param team the team
		 * @param player the player
		 * @param playerName the player name
		 * @param playerNo the player no
		 */
		public RuntimePlayerImpl(Team team, Player player, String playerName, String playerNo)
		{
			this.team = team;
			this.player = player;
			this.playerName = playerName;
			this.playerNo = playerNo;
		}

		
		
		/**
		 * Gets the Team.
		 *
		 * @return the Team
		 */
		public Team getTeam()
		{ return team; }

		/**
		 * Gets the player
		 *
		 * @return the player
		 */
		public Player getPlayer()
		{ return player; }

		/**
		 * Gets the player name.
		 *
		 * @return the player name
		 */
		public String getPlayerName()
		{ return playerName; }
		
		/**
		 * Gets the player no.
		 *
		 * @return the player no
		 */
		public String getPlayerNo() 
		{ return playerNo; }
		
		/**
		 * Gets the Javakicker Id.
		 *
		 * @return the Javakicker Id
		 */
		public String getJavaKickerId()
		{ return javaKickerId; }
		
		/**
		 * Gets the goals.
		 *
		 * @return the goals
		 */
		public String getGoals() 
		{ return goals.toString(); }
		
		/**
		 * Adds the goal.
		 */
		public void addGoal() 
		{ 
			goals++; 
		}
		
		/**
		 * Gets the touches.
		 *
		 * @return the touches
		 */
		public String getTouches()
		{ return touches.toString(); }
		
		/**
		 * Adds the touch.
		 */
		public void addTouch()
		{ 
			touches++; 
		}

		/**
		 * Gets the playerId.
		 *
		 * @return the player Id
		 */
		public String getPlayerId()
		{ return getTeam().getName() + ":" + getPlayerName(); }

		/**
		 * Gets the fouls.
		 *
		 * @return the fouls
		 */
		public int getFouls()
		{ return fouls; }

		/**
		 * Increments the fouls.
		 */
		public void incFouls()
		{ ++fouls; }

		/**
		 * Resets the fouls.
		 */
		public void resetFouls()
		{ fouls = 0; }

		/**
		 * Gets the Comment.
		 *
		 * @return the comment
		 */
		public String getComment()
		{ return comment; }

		/**
		 * Sets the comment.
		 *
		 * @param comment the new comment
		 */
		public void setComment(String comment)
		{ this.comment = comment; }

		/**
		 * Adds a Message.
		 */
		public void addMessage(Object sender, Object msg)
		{
			if (sender == lastSender)
				messages.removeLast();

			messages.addLast(msg);
			lastSender = sender;
		}

		/**
		 * Gets the Messages.
		 * 
		 * @return the messages
		 */
		public Object[] getMessages()
		{ return messages.toArray(); }

		/**
		 * Clear messages.
		 */
		public void clearMessages()
		{
			messages.clear();
			lastSender = null;
		}
	}

	/**
	 * Klasse, die Actions von Player Objekten durchfuehrt.
	 */
	private class PlayerMover implements Runnable
	{
		
		/** The player. */
		private Player player;
		
		/** The action. */
		private SoccerAction action = null;


		/**
		 * Instantiates a new player mover.
		 *
		 * @param player the player
		 */
		public PlayerMover(Player player)
		{ this.player = player; }


		/**
		 * Action method.
		 */
		public void run()
		{
			action = player.makeMove();
		}


		/**
		 * Gets the action.
		 *
		 * @return the action
		 */
		public SoccerAction getAction()
		{ return action; }
	}



	/** The Constant FIELD_WIDTH. (13 fields) */
	public static final int FIELD_WIDTH = 13;

	/** The Constant FIELD_HEIGHT. (15 fields) */
	public static final int FIELD_HEIGHT = 15;

	/** Ein zweidimensionales Array aus "Akteuren" - entweder <code>Actor</code>-Objekte oder <code>null</code>. */
	Actor[][] actors = new Actor[FIELD_WIDTH][FIELD_HEIGHT];

	/** Der Ball (ist auch ein Akteur). */
	private final Actor ball = new Actor()
	{};

	/** Alle Spieler als <code>RuntimePlayerImpl</code> Objekte. */
	private Collection<RuntimePlayerImpl> allPlayers;

	/** Die Spieler, nach Teams aufgeteilt. Ein Array aus 2 <code>Collection</code>s
	 * aus <code>RuntimePlayerImpl</code> Objekten */
	@SuppressWarnings("rawtypes")
	private List[] playersByTeam = new List[2];

	/** Aktuelle Spielminute. */
	private int minute = 0;

	/** Wie lange dauert das Spiel? Minuten = Anzahl Spielz�ge der Roboter. */
	public static final int GAME_MINUTES = 90;

	/** Zufallsgenerator. */
	Random random = new Random();

	/** Das Spiel, das berechnet wird. */
	private GamePlan.Match match;

	/** Das Ergebnis. */
	private GameResult result;

	/** Die Warteschlange f�r die Spieler (wer als n�chster seinen Zug ausf�hren darf). */
	private LinkedList<Object> priorityQueue = new LinkedList<Object>();

	/** Dummy-Objekt, das als Markierung f�r das Ende der Spielminute benutzt wird. */
	private static final Object NEXT_MINUTE = new Object() { };

	/** Das Recorder-Objekt, das die Aufzeichnung vornimmt. */
	private GameRecorder recorder;
	
	/** Legt fest, ob die Spieler immer an der gleichen Position starten **/ 
	private boolean debugmode = false;
	 

	/**
	 * Instantiates a new game engine.
	 *
	 * @param teams Die beiden Teams, die dem Programm als Argumente uebergeben wurden.
	 * @param lineups Lineups der beiden Teams.
	 * @param writer Writer mit dem die XML geschrieben wird.
	 * @param tolerateInvalidPlayers the tolerate invalid players
	 * @throws IOException Falls bei der Ausgabe �ber <code>Writer</code> Probleme auftreten.
	 * @throws ClassNotFoundException Wenn eine Klassendatei, die in der Team-Aufstellung referenziert wird, nicht gefunden wurde.
	 * @throws InvalidPlayerException Wenn eine Spielerklasse ung�ltig ist.
	 * @throws InvocationTargetException Wenn eine Spielerklasse im Konstruktor eine Exception wirft.
	 */
	public GameEngine(Team[] teams, XmlLineup[] lineups, Writer writer, boolean tolerateInvalidPlayers, boolean debugmode)
		throws IOException, ClassNotFoundException, InvalidPlayerException,
		InvocationTargetException
	{
		this.result = new GameResult();
		this.debugmode = debugmode;
		
		Team[] participants = teams;

		recorder = new XmlGameRecorder(writer);

		initialize(participants, lineups, tolerateInvalidPlayers); // spiel wird initialisiert mit allen spielern und lineups
	}


	/**
	 * Initialize.
	 *
	 * @param participants the participants
	 * @param lineups the lineups
	 * @param tolerateInvalidPlayers the tolerate invalid players
	 * @throws ClassNotFoundException the class not found exception
	 * @throws InvalidPlayerException the invalid player exception
	 * @throws InvocationTargetException the invocation target exception
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	@SuppressWarnings("unchecked")
	private void initialize(Team[] participants, Lineup[] lineups,
							boolean tolerateInvalidPlayers)
		throws ClassNotFoundException, InvalidPlayerException, InvocationTargetException, IOException
	{
		if (participants.length != 2) // hier stehen die Spielerklassen drin (mind. eine Pro Team)
			throw new IllegalArgumentException("Parameter participants muss ein Array der " +
				"L�nge 2 sein.");
		
		// hier steht das zusammengesetzte Lineup aus den Spielerklassen drin, aber keine Spielerobjekte
		if (lineups.length != 2) 
			throw new IllegalArgumentException("Parameter lineups muss ein Array der " +
				"L�nge 2 sein.");
		
		int javaKickerId = 0;
		for (int i = 0; i < 2; i++)
		{
			playersByTeam[i] = new ArrayList<RuntimePlayer>(5); // Arrayliste, die alle Spieler eines Teams beinhaltet

			Lineup.Player[] lineupPlayers = lineups[i].getPlayers();
			for (int j = 0; j < lineupPlayers.length &&
				 playersByTeam[i].size() <= 5; j++)
			{
				RuntimePlayer runtimePlayer = null;

				try
				{
					//runtime player ist der spieler, der gerade am zug ist
					//der naechste spieler auas dem spieler-array ist an der reihe
					runtimePlayer = new RuntimePlayerImpl(participants[i], lineupPlayers[j], javaKickerId++);
				}
				catch (ClassNotFoundException e)
				{
					if (!tolerateInvalidPlayers) //liegt eine invalide Spielerklasse vor, und tolerate ist false, wirf exception
						throw e;
				}
				catch (InvocationTargetException e)
				{
					if (!tolerateInvalidPlayers)
						throw e;
				}
				catch (InvalidPlayerException e)
				{
					if (!tolerateInvalidPlayers)
						throw e;
				}

				if (runtimePlayer != null)
					playersByTeam[i].add(runtimePlayer); 
			}

			/* Falls nicht genuegend (< 5) fehlerfreie Spieler in der Aufstellung waren,
			 * den Rest mit einer Standard-Spielerklasse auff�llen. */
			for (int j = playersByTeam[i].size(); j < 5; j++)
			{
				String playerName = "Spieler " + Integer.toString(j);
				RuntimePlayer runtimePlayer =
					new RuntimePlayerImpl(participants[i], new BasicPlayer(),
										  playerName, "42");
				playersByTeam[i].add(runtimePlayer);
			}
		}

		allPlayers = new ArrayList<RuntimePlayerImpl>(10);
		for (int i = 0; i < 2; i++)
			allPlayers.addAll(playersByTeam[i]);
		
		recorder.writeTeams(participants, allPlayers, new String[] { lineups[0].getJersey(), lineups[1].getJersey() });
	}
	
	/**
	 * Random shuffle.
	 *
	 * @param l the l
	 * @param seed the seed
	 */
	private void randomShuffle(List<Object> l, long seed)
	{
		Random random = new Random(seed);
		List<Object> r = new ArrayList<Object>(l.size());
		
		while (!l.isEmpty())
		{
			Object item = l.remove(random.nextInt(l.size()));
			r.add(item);
		}
		
		l.addAll(r);
	}


/**
 * hier wird das spiel durchgefuehrt .
 *	
 * @return the game result
 * @throws IOException Signals that an I/O exception has occurred.
 */
@SuppressWarnings("unchecked")
public GameResult play()
		throws IOException
	{
		long now = new Date().getTime();
		randomShuffle(playersByTeam[0], now >> 2);
		randomShuffle(playersByTeam[1], now << 2);
		
		
		/* Abwechselnd einen Spieler beider Teams in die Prio-Warteschlange
		 * aufnehmen. Die Reihenfolge wird danach nicht mehr veraendert */
		int startTeam = random.nextBoolean() ? 0 : 1;
		Iterator<RuntimePlayer> i1 = playersByTeam[startTeam].iterator(),
			i2 = playersByTeam[1 - startTeam].iterator();

		while (i1.hasNext())
		{
			priorityQueue.addLast(i1.next());
			priorityQueue.addLast(i2.next());
		}

		priorityQueue.addLast(NEXT_MINUTE);

		recorder.nextMinute();
		resetPositions();

		for (Iterator<RuntimePlayerImpl> i = allPlayers.iterator(); i.hasNext(); )
		{
			i.next().getPlayer().setGameInfo(this);
		}


		for (; ; )
		{
			try
			{
				if (!nextAction())
					break;
			}
			catch (IOException e)
			{
				throw e;
			}
			catch (Exception e)
			{}
		}

		return result;
	}
	
	
	/**
	 * Gets the team index.
	 *
	 * @param team the team
	 * @return the team index
	 */
	private int getTeamIndex(Team team)
	{
		if (match == null)
			return (team.equals(Team.TEAM1) ? 0 : 1);
		else
			return match.getParticipantIndex(team);
	}
	
	
	/**
	 * Adjust point.
	 *
	 * @param p the p
	 * @param team the team
	 * @return the point
	 */
	private Point adjustPoint(Point p, Team team)
	{
		int teamIndex = getTeamIndex(team);
		if (teamIndex == 0)
			return new Point(p);
		else if (teamIndex == 1)
			return new Point(FIELD_WIDTH - p.x - 1, FIELD_HEIGHT - p.y - 1);
		else
			return null;
	}


	/**
	 * Adjust point.
	 *
	 * @param p the p
	 * @param self the self
	 * @return the point
	 */
	private Point adjustPoint(Point p, RuntimePlayer self)
	{
		if (self != null)
			return adjustPoint(p, self.getTeam());
		else
			throw new IllegalArgumentException("Der Spieler darf nicht null sein.");
	}


	/**
	 * Adjust point.
	 *
	 * @param p the p
	 * @param self the self
	 * @return the point
	 */
	private Point adjustPoint(Point p, Player self)
	{
		if (self != null)
			return adjustPoint(p, findRuntimePlayer(self));
		else
			throw new IllegalArgumentException("Der Spieler darf nicht null sein.");
	}


/**
 * Spielerpositionen und blickrichtungen werden in diesen funktionen ober und unterhalb festgelegt / transponiert.
 *
 * @param direction the direction
 * @param team the team
 * @return the int
 */
private int adjustDirection(int direction, Team team)
	{
		int teamIndex = getTeamIndex(team);
		if (teamIndex == 0)
			return direction;
		else if (teamIndex == 1)
		{
			switch (direction)
			{
				case Player.LEFT:
					return Player.RIGHT;
				case Player.RIGHT:
					return Player.LEFT;
				case Player.UP:
					return Player.DOWN;
				case Player.DOWN:
					return Player.UP;
				case Player.NOTHING:
					return Player.NOTHING;
				default:
					throw new IllegalArgumentException("Ung�ltige Richtung!");
			}
		}
		else
			throw new IllegalArgumentException("Dieses Team nimmt gar nicht am Spiel teil!");
	}


	/**
	 * Adjust direction.
	 *
	 * @param direction the direction
	 * @param self the self
	 * @return the int
	 */
	private int adjustDirection(int direction, RuntimePlayer self)
	{
		if (self != null)
			return adjustDirection(direction, self.getTeam());
		else
			throw new IllegalArgumentException("Der Spieler darf nicht null sein.");
	}


	/**
	 * Find runtime player.
	 *
	 * @param player the player
	 * @return the runtime player
	 */
	private RuntimePlayer findRuntimePlayer(Player player)
	{
		Iterator<RuntimePlayerImpl> it = allPlayers.iterator();
		while (it.hasNext())
		{
			RuntimePlayer rtPlayer = it.next();
			if (rtPlayer.getPlayer() == player)
				return rtPlayer;
		}
		throw new AssertionError("Der Spieler ist nicht im Spiel!");
	}


	/**
	 * Gets the team for player.
	 *
	 * @param team the team
	 * @param self the self
	 * @return the team for player
	 */
	private Team getTeamForPlayer(int team, Player self)
	{
		RuntimePlayer rtPlayer = findRuntimePlayer(self);
		if (team == Player.MY_TEAM)
			return rtPlayer.getTeam();
		else if (team == Player.OTHER_TEAM)
		{
			if (match == null)
				return rtPlayer.getTeam().equals(Team.TEAM1) ? Team.TEAM2 : Team.TEAM1;
			else
				return match.getOpponent( rtPlayer.getTeam() );
		}
		else
			return null;
	}


	/** 
	  * Gets the field size.
	  * 
	  * @return the field size
	  */
	public Dimension getFieldSize()
	{
		return new Dimension(FIELD_WIDTH, FIELD_HEIGHT);
	}


	/** 
	  * Gets the Ballpos.
	  * 
	  * @return the ball pos
	  */
	public Point getBallPos(Player self)
	{
		return adjustPoint(ball.getPosition(), self);
	}


	/** 
	  * Gets the robo count.
	  * 
	  * @return the robo count
	  */
	public int getRobotCount()
	{
		return allPlayers.size();
	}
	
	
	/** 
	  * Gets the robo count.
	  * 
	  * @return the robo count
	  */
	public int getRobotCount(int team, Player self)
	{
		int teamIndex = getTeamIndex(getTeamForPlayer(team, self));
		try
		{
			return playersByTeam[teamIndex].size();
		}
		catch (IndexOutOfBoundsException e)
		{
			return -1;
		}
	}


	/**
	 * Gets the field robots.
	 *
	 * @param c the c
	 * @return the field robots
	 */
	private Collection<RuntimePlayer> getFieldRobots(Collection<RuntimePlayerImpl> c)
	{
		ArrayList<RuntimePlayer> fieldRobots = new ArrayList<RuntimePlayer>(c.size());
		for (Iterator<RuntimePlayerImpl> i = c.iterator(); i.hasNext();)
		{
			RuntimePlayer player = i.next();
			if (player.getPosition() != null)
				fieldRobots.add(player);
		}
		return fieldRobots;
	}


	/** 
	  * Gets the fieldrobots.
	  * 
	  * @return the fieldrobots
	  */
	public Collection<RuntimePlayer> getFieldRobots()
	{
		return getFieldRobots(allPlayers);
	}


	/** 
	  * Gets the fieldrobots.
	  * 
	  * @return the fieldrobots
	  */
	@SuppressWarnings("unchecked")
	public Collection<RuntimePlayer> getFieldRobots(int team, Player self)
	{
		int teamIndex = getTeamIndex( getTeamForPlayer(team, self) );
		try
		{
			return getFieldRobots(playersByTeam[teamIndex]);
		}
		catch (IndexOutOfBoundsException e)
		{
			return null;
		}
	}


	/** 
	  * Gets the score difference.
	  * 
	  * @return the score difference
	  */
	public int getScoreDifference(Player self)
	{
		int teamIndex = getTeamIndex( getTeamForPlayer(Player.MY_TEAM, self) );
		return result.getDifference(teamIndex);
	}


	/**
	 * Gets the player team.
	 *
	 * @param self the self
	 * @param otherPlayer the other player
	 * @return the player team
	 */
	public int getPlayerTeam(Player self, Player otherPlayer)
	{
		RuntimePlayer rtSelf = findRuntimePlayer(self),
					  rtOther = findRuntimePlayer(otherPlayer);
		if (rtSelf != null && rtOther != null)
		{
			return rtSelf.getTeam().equals(rtOther.getTeam()) ?
				Player.MY_TEAM : Player.OTHER_TEAM;
		}
		else
			return Player.NOTHING;
	}

	/** 
	  * Ueberpruefe die Felder um einen Player herum.
	  * 
	  * @return the Cell info
	  */
public int getCellInfo(Player self, Point p)
	{
		p = adjustPoint(p, self);

		if (!isPointInField(p))
			return Player.WALL;

		Actor actor = getActorAt(p);
		if (actor == null)
			return Player.FREE;
		else if (actor == ball)
			return Player.BALL;
		else if (actor instanceof RuntimePlayer)
		{
			RuntimePlayer selfPlayer = findRuntimePlayer(self);
			RuntimePlayer fieldPlayer = (RuntimePlayer) actor;

			if (selfPlayer != null &&
				selfPlayer.getTeam().equals(fieldPlayer.getTeam()))
				return Player.MY_TEAM;
			else
				return Player.OTHER_TEAM;
		}
		else
			// wird nie aufgerufen
			return Player.NOTHING;
	}


/** 
 * Gets the player at a point.
 * 
 * @return the player at a point
 */
	public RuntimePlayer getPlayerAt(Point p, Player self)
	{
		p = adjustPoint(p, self);
		
		Actor a = getActorAt(p);
		
		if (a instanceof RuntimePlayer)
			return (RuntimePlayer) a;
		else
			return null;
	}


	/** 
	 * Gets the player position.
	 * 
	 * @return the player position
	 */
	public Point getPlayerPosition(Player player)
	{
		RuntimePlayer rtPlayer = findRuntimePlayer(player);
		if (rtPlayer == null)
			return null;
		else
			return adjustPoint(rtPlayer.getPosition(), player);
	}


	/** 
	 * Gets the player priority.
	 * 
	 * @return the player priority
	 */
	public int getPlayerPriority(Player player)
	{
		RuntimePlayer rtPlayer = findRuntimePlayer(player);
		if (rtPlayer != null)
			return priorityQueue.indexOf(rtPlayer);
		else
			throw new AssertionError("Der Spieler ist nicht im Spiel!");
	}


	/**
	 * Gets the actor at.
	 *
	 * @param pos the pos
	 * @return the actor at
	 */
	private Actor getActorAt(Point pos)
	{
		return actors[pos.x][pos.y];
	}


	/**
	 * Sets the actor at.
	 *
	 * @param pos the pos
	 * @param actor the actor
	 */
	private void setActorAt(Point pos, Actor actor)
	{
		if (actor != null)
			actor.setPosition(pos);
		if (pos != null)
			actors[pos.x][pos.y] = actor;
	}


	/**
	 * Move actor.
	 *
	 * @param actor the actor
	 * @param dest the dest
	 * @return true, if successful
	 */
	private boolean moveActor(Actor actor, Point dest)
	{
		if (actor == null)
			throw new IllegalArgumentException();

		if (dest != null && getActorAt(dest) != null)
			return false;
		else
		{
			Point source = actor.getPosition();
			setActorAt(source, null);
			setActorAt(dest, actor);
			return true;
		}
	}


	/**
	 * Clear field.
	 */
	private void clearField()
	{
		Point p = new Point();
		for (p.x = 0; p.x < FIELD_WIDTH; p.x++)
			for (p.y = 0; p.y < FIELD_HEIGHT; p.y++)
			{
				Actor actor = getActorAt(p);
				if (actor != null)
					actor.setPosition(null);
				setActorAt(p, null);
			}
	}


	/**
	 * Sets the random position.
	 *
	 * @param player the new random position
	 */
	private void setRandomPosition(RuntimePlayerImpl player)
	{
		Point newPos;
		do
		{
			newPos = new Point(random.nextInt(FIELD_WIDTH),
							   FIELD_HEIGHT - 1 - random.nextInt(FIELD_HEIGHT / 3));
			newPos = adjustPoint(newPos, player);
		}
		while (!moveActor(player, newPos));
	}


	/**
	 * Reset ball position.
	 */
	private void resetBallPosition()
	{
		Point newBallPos = new Point(FIELD_WIDTH / 2, FIELD_HEIGHT / 2);

		moveActor(ball, newBallPos);
	}

	/**
	 * Reset positions.
	 *	Halbzeit Reset
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void resetPositions()
		throws IOException
	{
		clearField();
		resetBallPosition();
		
		int playerCounter = 0;
		for (Iterator<RuntimePlayerImpl> i = allPlayers.iterator(); i.hasNext(); )
		{
			RuntimePlayerImpl player = i.next();
			player.resetFouls();			//TODO resetFouls to lassen?
			if(debugmode) {
				if(playerCounter == 5) playerCounter = 0;
				Point newPos = new Point(5+playerCounter,13);
				playerCounter++;
				newPos = adjustPoint(newPos, player);
				moveActor(player, newPos);
			}
			else {
				setRandomPosition(player);
			}
		}

		recorder.writeResetPositions(allPlayers); 
	}

/**
 * Offset point: gib das naechste feld in blickrichtung des spielers.
 *
 * @param start the start
 * @param direction the direction
 * @param dist the dist
 * @return the point
 */
private Point offsetPoint(Point start, int direction, int dist)
	{
		switch (direction)
		{
			case Player.LEFT:
				return new Point(start.x - dist, start.y);
			case Player.RIGHT:
				return new Point(start.x + dist, start.y);
			case Player.UP:
				return new Point(start.x, start.y - dist);
			case Player.DOWN:
				return new Point(start.x, start.y + dist);
			case Player.NOTHING:
				return new Point(start);
			default:
				throw new IllegalArgumentException("Ung�ltige Richtung");
		}
	}


	/**
	 * Offset point.
	 *
	 * @param start the start
	 * @param direction the direction
	 * @return the point
	 */
	private Point offsetPoint(Point start, int direction)
	{
		return offsetPoint(start, direction, 1);
	}


	/**
	 * Checks if is point in field.
	 *
	 * @param p the p
	 * @return true, if is point in field
	 */
	private boolean isPointInField(Point p)
	{
		return (p.x >= 0) && (p.x < FIELD_WIDTH) &&
			(p.y >= 0) && (p.y < FIELD_HEIGHT);
	}

	/**
	 *hier werden fouls gezaehlt und spieler ggf vom spielfeld entfernt.
	 *
	 * @param player the player
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void commitFoul(RuntimePlayerImpl player)
		throws IOException
	{
		//player.incFouls();
		Point dest = new Point(-1,-1);
		if (player.getFouls() == 3)
			moveActor(player, dest);
	}

/**
 * zaehlt die Tore pro Team
 *
 * @param player the player
 * @param ownGoal the own goal
 * @throws IOException Signals that an I/O exception has occurred.
 */
private void scoreGoal(RuntimePlayer player, boolean ownGoal)
		throws IOException
	{
		RuntimePlayerImpl javaKicker = (RuntimePlayerImpl) player;
		javaKicker.addGoal();
		
		int teamIndex = getTeamIndex( getTeamForPlayer(ownGoal ? Player.OTHER_TEAM : Player.MY_TEAM, player.getPlayer() ));
		result.addGoal(teamIndex);		
	}


/**
 * zaehlt die tore pro player
 *
 * @param player the player
 * @throws IOException Signals that an I/O exception has occurred.
 */
private int checkForGoal(RuntimePlayer player)
		throws IOException
	{
		Point adjBallPos = adjustPoint(ball.getPosition(), player);
		if (adjBallPos.y == 0) {
			scoreGoal(player, false);
			return 1;
		}
		else if (adjBallPos.y == FIELD_HEIGHT - 1) {
			scoreGoal(player, true);
			return 2;
		}
		return 0;
	}

	/**
	 * beim Foul Akteure vertauschen.
	 *
	 * @param player the player
	 * @param otherActor the other actor
	 * @param direction the direction
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void swapActors(RuntimePlayerImpl player, Actor otherActor, int direction)
		throws IOException
	{
		Point playerPos = player.getPosition(),
			otherActorPos = otherActor.getPosition();

		if (!moveActor(player, null)
			|| !moveActor(otherActor, playerPos)
			|| !moveActor(player, otherActorPos))
		{
			throw new AssertionError();
		}

		if (otherActor instanceof RuntimePlayer) {
			player.incFouls();
			
			recorder.writeAction(player, SoccerAction.MOVE, otherActor.getPosition(), direction, ((RuntimePlayerImpl)otherActor).getJavaKickerId(), 0, true);
			commitFoul(player);
		}
		else if (otherActor == ball) {
			int goal = checkForGoal(player);
			player.addTouch();
			recorder.writeAction(player, SoccerAction.MOVE, otherActor.getPosition(), direction, null, goal, false);
			if(goal != 0) resetPositions();
		}
	}

	/**
	 * bewegungsroutine fuer move
	 *
	 * @param player the player
	 * @param direction the direction
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void handleMove(RuntimePlayerImpl player, int direction)
		throws IOException
	{
		if (direction == Player.NOTHING) {
			recorder.writeAction(player, SoccerAction.NOTHING, null, direction, null, 0, false);
			return;
		}

		Point playerPos = player.getPosition();
		Point target = offsetPoint(playerPos, direction);

		/* Roboter laeuft gegen eine Wand: Nichts passiert */
		if (!isPointInField(target)){
			recorder.writeAction(player, SoccerAction.NOTHING, null, direction, null, 0, false);
			return;
		}

		/* Roboter hat ein Hindernis vor sich. */
		if (getActorAt(target) != null)
		{
			Point behindTarget = offsetPoint(target, direction);

			if (!isPointInField(behindTarget) ||
					 getActorAt(behindTarget) != null)
			{
				/* Das Feld dahinter ist entweder eine Wand, oder auch besetzt.
				 * Dann tauscht der Roboter mit dem ersten Hindernis das Feld. */
				swapActors(player, getActorAt(target), direction);
			}
			else
			{
				/* Roboter hat ein Hindernis vor sich, aber das Feld dahinter ist
				 * leer. Dann wird das Hindernis ein Feld weitergeschoben */
				Actor pushedActor = getActorAt(target);
				if (!moveActor(pushedActor, behindTarget))
					throw new AssertionError();
				if (!moveActor(player, target))
					throw new AssertionError();
				//recorder.writeAction(player, SoccerAction.MOVE, target, direction);  //player: id,x,y,direction,goals,cards,contacts - 

				if (pushedActor == ball) {
					player.addTouch();
					int goal = checkForGoal(player);
					recorder.writeAction(player, SoccerAction.MOVE, pushedActor.getPosition(), direction, null, goal, false);
					if(goal!=0) resetPositions();
				}
				else if(pushedActor instanceof RuntimePlayer) {
					recorder.writeAction(player, SoccerAction.MOVE, pushedActor.getPosition(), direction, ((RuntimePlayerImpl)pushedActor).getJavaKickerId(), 0, false);
				}
			}
		}
		else
		{
			/* Roboter hat ein freies Feld vor sich. */
			if (!moveActor(player, target))
			{
				throw new AssertionError();
			}
			recorder.writeAction(player, SoccerAction.MOVE, null, direction, null, 0, false); //player: id,x,y,direction,goals,cards,contacts
		}
	}

	//bewegungsroutine fuer run
	/**
	 * Handle run.
	 *
	 * @param player the player
	 * @param direction the direction
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void handleRun(RuntimePlayerImpl player, int direction)
		throws IOException
	{
		Point playerPos = player.getPosition();

		Point halfwayTarget = offsetPoint(playerPos, direction);
		Point target = offsetPoint(playerPos, direction, 2);

		/* Falls zwischen Roboter und Wand keine zwei Felder mehr liegen,
		 * oder eines der beiden Felder besetzt ist, wird ein MOVE ausgefuehrt */
		if (!isPointInField(target) || getActorAt(halfwayTarget) != null ||
			getActorAt(target) != null)
		{
			handleMove(player, direction);
		}
		else
		{
			moveActor(player, target);
			recorder.writeAction(player, SoccerAction.RUN, null, direction, null, 0, false);
		}
	}

	/**
	 * bewegungsroutine fuers dribbeln
	 *
	 * @param player the player
	 * @param direction the direction
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private void handleDribble(RuntimePlayerImpl player, int direction)
		throws IOException
	{
		Point playerPos = player.getPosition(),
			ballPos = ball.getPosition();

		// ueberpruefen, ob der Ball neben dem Roboter liegt
		if (Math.abs(ballPos.x - playerPos.x) <= 1 &&
			Math.abs(ballPos.y - playerPos.y) <= 1)
		{
			Point target = offsetPoint(playerPos, direction);

			if (isPointInField(target) &&
					 !target.equals(ballPos) && getActorAt(target) == null)
			{
				moveActor(ball, target);
				player.addTouch();
				// player: id,x,y,direction,goals,cards,contacts
				
				int goal = checkForGoal(player);
				recorder.writeAction(player, SoccerAction.DRIBBLE, ball.getPosition(), direction, null, goal, false);
				if(goal!=0) resetPositions();
			}
		}
	}


/**
 * Pass-Routine
 *
 * @param player the player
 * @param direction the direction
 * @throws IOException Signals that an I/O exception has occurred.
 */
private void handlePass(RuntimePlayerImpl player, int direction)
		throws IOException
	{
		Point playerPos = player.getPosition();
		Point ballPos = ball.getPosition();

		Point p = offsetPoint(playerPos, direction);
		if (!p.equals(ballPos))
			return;

		boolean canPass = true;
		for (Point next = offsetPoint(p, direction); canPass;
			 p = next, next = offsetPoint(p, direction))
		{
			if (!isPointInField(next))
				canPass = false;
			else
			{
				Actor actor = getActorAt(next);
				if (actor != null)
				{
					/* Es muss sich hier um einen Spieler handeln, denn der Ball
					 * liegt ja woanders... */
					RuntimePlayer otherPlayer = (RuntimePlayer) actor;
					if (otherPlayer.getTeam().equals(player.getTeam()))
						break;
					else
						canPass = false;
				}
			}
		}

		if (canPass)
		{
			moveActor(ball, p);
			player.addTouch();
			recorder.writeAction(player, SoccerAction.PASS, ball.getPosition(), direction, null, 0, false); //player: id,x,y,direction,goals,cards,contacts 
		}
		else
		{
			p = offsetPoint(ballPos, direction);

			if (isPointInField(p) &&
					 getActorAt(p) == null)
			{
				moveActor(ball, p);
				player.addTouch();
				int goal = checkForGoal(player);
				recorder.writeAction(player, SoccerAction.PASS, ball.getPosition(), direction, null, goal, false);  //player: id,x,y,direction,goals,cards,contacts
				if(goal!=0) resetPositions();
			}
		}
	}


	// legt die zugereihenfolge der Spieler fest - bzw den Spielablauf
	/**
	 * Next action.
	 *
	 * @return true, if successful
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	private boolean nextAction()
		throws IOException
	{
		// Objekt vom Anfang der Warteschlange holen ...
		Object nextQueuedItem = priorityQueue.removeFirst();
		// ...und gleich wieder hinten anstellen (fuer die naechste Spielminute)
		priorityQueue.addLast(nextQueuedItem);


		if (nextQueuedItem == NEXT_MINUTE)
		{
			/* Wenn der Marker fuer das Ende der Spielminute gefunden wurde: dieses
			 * aufzeichnen */
			if (++minute < GAME_MINUTES)
			{
				// Wenn das Spiel noch laeuft: Minute im recorder hochzaehlen
				recorder.nextMinute(); 

				if (minute == GAME_MINUTES / 2)
				{
					// Halbzeit erreicht? Dann werden die Spieler zurueckgesetzt
					resetPositions();
				}

				return true;
			}
			else
			{
				// Spiel ist zu Ende
				recorder.close();
				return false;		// Es gibt keine weiteren Spielzuege
			}
		}
		else
		{
			// Ein Spieler ist am Anfang der Warteschlange.
			RuntimePlayerImpl player = (RuntimePlayerImpl) nextQueuedItem;

			// Den Spieler seine Aktion ausfuehren lassen...
			SoccerAction soccerAction = null;

			if (player.getFouls() == 3){	// ...falls er eine rote Karte hat
				recorder.writeAction(player, SoccerAction.NOTHING, null, 0, null, 0, false);
				Point pos = new Point(-1,-1);
				player.setPosition(pos);
				return true;
			}
			
			try
			{
				PlayerMover mover = new PlayerMover(player.getPlayer());
				Thread playerThread = new Thread(mover);
				playerThread.start();

				// Maximal eine Sekunde auf den Spieler warten
				playerThread.join(1000);

				if (playerThread.isAlive())
				{
					playerThread.interrupt();
					recorder.writeComment("Fehler beim Spieler " + player.getPlayerName() +
						": Timeout erreicht.");
				}

				soccerAction = mover.getAction();
			}
			catch(Throwable t)
			{
				recorder.writeComment("Fehler beim Spieler " + player.getPlayerName() +
					": " + t.getClass().toString() + ": " + t.getMessage());
			}

			// Nachrichten an den Spieler loeschen
			player.clearMessages();

			if (soccerAction != null)
			{
				String action = soccerAction.getAction();
				int direction = adjustDirection(soccerAction.getDirection(), player);

				// Den entsprechenden Aktions-Handler aufrufen
				if (action == SoccerAction.MOVE)
					handleMove(player, direction);
				else if (action == SoccerAction.RUN)
					handleRun(player, direction);
				else if (action == SoccerAction.DRIBBLE)
					handleDribble(player, direction);
				else if (action == SoccerAction.PASS)
					handlePass(player, direction);
			}
			else
			{
//				soccerAction.setAction("NOTHING");
//				soccerAction.setDirection(0);
				recorder.writeAction(player, "NOTHING", null, 0, null, 0, false);
			}

			return true;
		}
	}
}
